﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace SSFT
{
	public partial class MainForm : Form
	{
		string[] startup_args;
		Timer gui_timer;

		bool client_options_enabled;
		bool server_options_enabled;

		const int GuiTimerInterval = 125;

		AboutForm about_form;

		public MainForm(string[] args)
		{
			InitializeComponent();
			startup_args = args;

			about_form = new AboutForm();
		}

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			// Use file from command line.
			if (startup_args.Length > 0)
				ServerSelectedFile = startup_args[0];

			// Set gui states.
			ServerOptionsEnabled = !Program.Server.IsRunning;
			ClientOptionsEnabled = !Program.Client.IsRunning;

			// Start gui timer.
			gui_timer = new Timer();
			gui_timer.Interval = GuiTimerInterval;
			gui_timer.Tick += new EventHandler(gui_timer_Tick);
			gui_timer.Start();

			// Set initial values.
            CliHostnameText.Text = Program.Settings.Get("Client", "hostname");
            CliPortText.Text = Program.Settings.Get("Client", "port");
			ServPortText.Text = Program.Settings.Get("Server", "port");
			ServMaxClientText.Text = Program.Settings.Get("Server", "maxconns");
		}

		protected override void OnClosing(CancelEventArgs e)
		{
			base.OnClosing(e);

			// Stop client and server, and wait for them
			// to finish.
			Program.Server.Stop();
			Program.Client.Disconnect();
			while (Program.Server.IsRunning || Program.Client.IsRunning)
				Application.DoEvents();
		}

		public void ServerLog(string msg)
		{
			Invoke(new Action(delegate()
				{
					string time = DateTime.Now.ToString("HH:mm:ss");

					ListViewItem item = new ListViewItem(time);
					item.SubItems.Add(msg);
					ServLogList.Items.Add(item);
                    item.EnsureVisible();
				}));
		}

		public void ClientLog(string msg)
		{
			Invoke(new Action(delegate()
				{
					string time = DateTime.Now.ToString("HH:mm:ss");

					ListViewItem item = new ListViewItem(time);
					item.SubItems.Add(msg);
					CliLogList.Items.Add(item);
                    item.EnsureVisible();
				}));
		}

		public void ServerAddConnection(SingleFileServer.Connection connection)
		{
			Invoke(new Action(delegate()
				{
					ListViewItem item = new ListViewItem(string.Empty);
					item.SubItems.Add(string.Empty);
					item.SubItems.Add(string.Empty);
					item.SubItems.Add(string.Empty);
					item.Tag = connection;
					ServConnectionList.Items.Add(item);
				}));
		}

		public void ServerRemoveConnection(SingleFileServer.Connection connection)
		{
			Invoke(new Action(delegate()
				{
					foreach (ListViewItem item in ServConnectionList.Items)
						if (item.Tag == connection)
						{
							item.Remove();
							break;
						}
				}));
		}

		private string ServerSelectedFile
		{
			get { return ServFileSelectText.Text; }
			set
			{
				try
				{
					ServFileSelectText.Text = value;
					ServFileNameText.Text = Path.GetFileName(value);
					ServFileSizeText.Text = Util.BytesToString(new FileInfo(value).Length);
					this.MainTabControl.SelectedTab = this.ServerTab;
				}
				catch (Exception)
				{
					ServFileSelectText.Text = string.Empty;
					ServFileNameText.Text = string.Empty;
					ServFileSizeText.Text = string.Empty;
					Util.SSFTWarning("Failure to locate the specified file.");
				}
			}
		}

		public bool ServerOptionsEnabled
		{
			get { return server_options_enabled; }
			set
			{
				// Since this method can be called from any thread,
				// we have to invoke the gui thread to set components.
				Invoke(new Action(delegate()
					{
						if (server_options_enabled = value)
						{
							ServFileGroup.Enabled = true;
							ServSettingsGroup.Enabled = true;
							ServConnectionGroup.Visible = false;
							ServStartButton.Text = "Start Server";
						}
						else
						{
							ServFileGroup.Enabled = false;
							ServSettingsGroup.Enabled = false;
							ServConnectionGroup.Visible = true;
							ServStartButton.Text = "Stop Server";
						}
					}));
			}
		}

		public bool ClientOptionsEnabled
		{
			get { return client_options_enabled; }
			set
			{
				// Since this method can be called from any thread,
				// we have to invoke the gui thread to set components.
				Invoke(new Action(delegate()
					{
						if (client_options_enabled = value)
						{
							CliConnGroup.Enabled = true;
							CliProgressGroup.Visible = false;
							CliConnButton.Text = "Connect";
							MainStatusClient.Text = "Client disconnected";
							MainStatusClient.ImageIndex = 1;
						}
						else
						{
							CliConnGroup.Enabled = false;
							CliConnButton.Text = "Disconnect";
							MainStatusClient.Text = "Client connected";
							MainStatusClient.ImageIndex = 0;
						}
					}));
			}
		}

		public void ClientRequestFilepath(string orig_filename)
		{
			Invoke(new Action(delegate()
				{
					// Set file name in dialog.
					MainSaveFileDialog.FileName = orig_filename;
					
					// Determine and set file extension.
					string ext = Path.GetExtension(orig_filename).ToLower();
					if (string.IsNullOrEmpty(ext))
						MainSaveFileDialog.Filter = string.Empty;
					else
						MainSaveFileDialog.Filter = ext + "|" + "*" + ext;
					
					// Get dialog result.
					DialogResult result = MainSaveFileDialog.ShowDialog();
					if (result == DialogResult.OK)
						Program.Client.Filepath = MainSaveFileDialog.FileName;
					else
						Program.Client.Disconnect();
				}));
		}

		private void gui_timer_Tick(object sender, EventArgs e)
		{
			if (Program.Client.Progress != null)
			{
				ProgressInfo progress = Program.Client.Progress;

				CliProgressGroup.Visible = true;
				CliFileNameText.Text = progress.Filename;
				CliFileSizeText.Text = Util.BytesToString(progress.Filesize);

				CliSpeedText.Text = Util.BytesToString(progress.BytesPerSecond) + "/s";
				CliFileRecvText.Text = Util.BytesToString(progress.BytesTransferred);

				CliFileProgress.Value = (int)progress.Percent;
				CliFilePercent.Text = string.Format("{0:0.0} %", (float)progress.Percent);
			}

			foreach (ListViewItem item in ServConnectionList.Items)
			{
				SingleFileServer.Connection connection = item.Tag as SingleFileServer.Connection;
				if (connection.IsRunning)
				{
					if (connection.Endpoint != null)
					{
						item.SubItems[0].Text = connection.Endpoint.Address.ToString();
					}
					if (connection.Progress != null)
					{
						item.SubItems[1].Text = string.Format("{0:0.0} %", (float)connection.Progress.Percent);
						item.SubItems[2].Text = Util.BytesToString(connection.Progress.BytesTransferred);
						item.SubItems[3].Text = Util.BytesToString(connection.Progress.BytesPerSecond) + "/s";
					}
				}
			}
		}

		private void ServFileSelectButton_Click(object sender, EventArgs e)
		{
			// Open the file dialog.
			DialogResult ret = MainOpenFileDialog.ShowDialog();
			if (ret == DialogResult.OK)
			{
				// Set file info.
				string filepath = MainOpenFileDialog.FileName;
				ServerSelectedFile = filepath;
			}
		}

		private void ServStartButton_Click(object sender, EventArgs e)
		{
			int port;
			string filepath;
			int max_connections;

			// If the server is running, this button has a stop effect.
			if (Program.Server.IsRunning)
			{
				Program.Server.Stop();
				return;
			}

			// Get port number.
			if (!int.TryParse(ServPortText.Text, out port))
			{
				Util.SSFTWarning("Please enter a valid port number.");
				return;
			}

			// If no file is selected when server start button
			// is pressed, query user for file.
			if (string.IsNullOrEmpty(filepath = ServFileSelectText.Text))
			{
				DialogResult ret = MainOpenFileDialog.ShowDialog();
				if (ret == DialogResult.OK)
				{
					// Set file info.
					filepath = MainOpenFileDialog.FileName;
					ServerSelectedFile = filepath;
				}
				else
					return;
			}
			// If file does not exist, notify the user.
			if (!File.Exists(ServFileSelectText.Text))
			{
				Util.SSFTWarning("Failure to locate the specified file.");
				return;
			}

			if (!(int.TryParse(ServMaxClientText.Text, out max_connections) && max_connections > -1))
			{
				Util.SSFTWarning("Please enter a valid number for max server connections.");
				return;
			}

            // Save settings.
            Program.Settings.Set("Server", "port", port.ToString());
            Program.Settings.Set("Server", "maxconns", max_connections.ToString());

			// Start server.
			Program.Server.MaxConnections = max_connections;
			Program.Server.Start(port, filepath);
		}

		private void ServFileGroup_DragDrop(object sender, DragEventArgs e)
		{
			string[] filepaths = e.Data.GetData(DataFormats.FileDrop) as string[];
			if (filepaths.Length > 1)
				Util.SSFTWarning("Only one file can be served at once.");
			else
				ServerSelectedFile = filepaths[0];
		}

		private void ServFileGroup_DragEnter(object sender, DragEventArgs e)
		{
			if (e.Data.GetDataPresent(DataFormats.FileDrop, false))
				e.Effect = DragDropEffects.All;
		}

		private void CliConnButton_Click(object sender, EventArgs e)
		{
			string hostname;
			int port;

			// If the client is running, this button has
			// a disconnecting effect.
			if (Program.Client.IsRunning)
			{
				Program.Client.Disconnect();
				return;
			}

			if (string.IsNullOrEmpty(hostname = CliHostnameText.Text))
			{
				Util.SSFTWarning("Please enter a valid host name or IP address.");
				return;
			}

			if (!int.TryParse(CliPortText.Text, out port))
			{
				Util.SSFTWarning("Please enter a valid port number.");
				return;
			}

			// Save settings.
            Program.Settings.Set("Client", "hostname", hostname);
            Program.Settings.Set("Client", "port", port.ToString());
            
            // Connect client.
            Program.Client.Connect(hostname, port);
		}

		private void ServConnectionList_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.KeyCode == Keys.Delete)
			{
				foreach (ListViewItem item in ServConnectionList.SelectedItems)
				{
					(item.Tag as SingleFileServer.Connection).Stop();
				}
			}
		}

		private void ServConnectionListContextMenuRemoveItem_Click(object sender, EventArgs e)
		{
			ListView list = ServConnectionList;
			foreach (ListViewItem item in list.SelectedItems)
			{
				(item.Tag as SingleFileServer.Connection).Stop();
			}
		}

		private void ServConnectionList_MouseClick(object sender, MouseEventArgs e)
		{
			ListView list = ServConnectionList;
			if (e.Button == MouseButtons.Right && list.SelectedItems.Count > 0)
				ServConnectionListContextMenu.Show(list, e.Location);
		}

		private void exitToolStripMenuItem_Click(object sender, EventArgs e)
		{
			Close();
		}

		private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
		{
			about_form.Show();
		}
	}
}
